// Copyright (c) 2007-2008 Michael Buckley

// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:

// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

#import "BTLSocketManager.h"
#import "BTLSocketManager+Protected.h"

//! The BTLSocketManager class manages a set of connected sockets, connecting
//! sockets, and listening sockets, updrating them each time the select()
//! method is called.

@implementation BTLSocketManager

#ifdef KEEP_UNDEFINED
#pragma mark Initialization
#endif

- (id)init
{
	self = [super init];
	protectedConnectedSockets = [[NSMutableArray alloc] init];
	protectedConnectingSockets = [[NSMutableArray alloc] initWithCapacity:1];
	protectedListeningSockets = [[NSMutableArray alloc] initWithCapacity:1];
	privateHighestSocket = 0;
	FD_ZERO(&privateSocketSet);
	return self;
}

#ifdef KEEP_UNDEFINED
#pragma mark Socket Management
#endif

- (void)addConnectedSocket:(BTLSocket*)aSocket
{
	[[self protectedConnectedSockets] addObject:aSocket];
	if([aSocket socketDescriptor] > privateHighestSocket){
		privateHighestSocket = [aSocket socketDescriptor];
	}
	FD_SET([aSocket socketDescriptor], &privateSocketSet);
}

- (void)removeConnectedSocket:(BTLSocket*)aSocket
{
	FD_CLR([aSocket socketDescriptor], &privateSocketSet);
	privateHighestSocket = 0;
	
	[[self protectedConnectedSockets] removeObject:aSocket];
	
	NSEnumerator* enumerator = [[self protectedConnectedSockets] objectEnumerator];
	BTLSocket* socket;
	while(socket = [enumerator nextObject]){
		if([socket socketDescriptor] > privateHighestSocket){
			privateHighestSocket = [socket socketDescriptor];
		}
	}
}

- (void)addListeningSocket:(BTLConnectionOrientedSocket*)aSocket
{
	[[self protectedListeningSockets] addObject:aSocket];
}

- (void)removeListeningSocket:(BTLConnectionOrientedSocket*)aSocket
{
	[[self protectedListeningSockets] removeObject:aSocket];
}

- (void)addConnectingSocket:(BTLConnectionOrientedSocket*)aSocket
{
	[[self protectedConnectingSockets] addObject:aSocket];
}

- (void)removeConnectingSocket:(BTLConnectionOrientedSocket*)aSocket
{
	[[self protectedConnectingSockets] removeObject:aSocket];
}

//! \brief Removes the socket from the manager completely.

- (void)removeSocket:(BTLSocket*)aSocket
{	
	[self removeListeningSocket:(BTLConnectionOrientedSocket*) aSocket];
	[self removeConnectingSocket:(BTLConnectionOrientedSocket*) aSocket];
	[self removeConnectedSocket:(BTLConnectionOrientedSocket*) aSocket];
}

#ifdef KEEP_UNDEFINED
#pragma mark Deallocation
#endif

//! \brief Updates the sockets.
//!
//! This method should be called often. Managed sockets will not recieve data or
//! finish pending connections until this method is called. Placing a call to
//! this method in the applicationWillUpdate: or applicationDidUpdate: method of
//! an NSApplication delegate should accomplish this.

- (void)select
{
	NSEnumerator* enumerator = [[self protectedListeningSockets] objectEnumerator];
	BTLSocket* currentObject;
	
	while(currentObject = [enumerator nextObject]){
		BTLConnectionOrientedSocket* newSocket = [((BTLConnectionOrientedSocket*) currentObject) accept];
		while(newSocket != nil){
			newSocket = [((BTLConnectionOrientedSocket*) currentObject) accept];
		}
	}
	
	enumerator = [[self protectedConnectingSockets] objectEnumerator];
	while(currentObject = [enumerator nextObject]){
		[currentObject connectToAddress:nil withTimeout:nil];
	}
	
	fd_set set = privateSocketSet;
	struct timeval timeout;
	memset(&timeout, 0, sizeof(struct timeval));
	while(select(privateHighestSocket + 1, &set, NULL, NULL, &timeout) > 0){
		enumerator = [[self protectedConnectedSockets] objectEnumerator];
		while(currentObject = [enumerator nextObject]){
			if(FD_ISSET([currentObject socketDescriptor], &set)){
				[currentObject read];
			}
		}
		set = privateSocketSet;
	}
}

#ifdef KEEP_UNDEFINED
#pragma mark Deallocation
#endif

- (void)dealloc
{
	if(protectedConnectedSockets != nil){
		[protectedConnectedSockets release];
	}
	
	if(protectedListeningSockets != nil){
		[protectedListeningSockets release];
	}
	
	if(protectedConnectingSockets != nil){
		[protectedConnectingSockets release];
	}
	
	[super dealloc];
}

@end

@implementation BTLSocketManager (Protected)

#ifdef KEEP_UNDEFINED
#pragma mark Protected Accessors
#endif

- (NSMutableArray*)protectedListeningSockets
{
	return protectedListeningSockets;
}

- (NSMutableArray*)protectedConnectingSockets
{
	return protectedConnectingSockets;
}

- (NSMutableArray*)protectedConnectedSockets
{
	return protectedConnectedSockets;
}

@end
